package cz.muni.fi.pa165.languageschool.service.impl;

import cz.muni.fi.pa165.languageschool.DataFetchException;
import cz.muni.fi.pa165.languageschool.utils.ETOConverter;
import cz.muni.fi.pa165.languageschool.Lecture;
import cz.muni.fi.pa165.languageschool.dto.LectureTO;
import cz.muni.fi.pa165.languageschool.Lecturer;
import cz.muni.fi.pa165.languageschool.Student;
import cz.muni.fi.pa165.languageschool.dto.LecturerTO;
import cz.muni.fi.pa165.languageschool.dao.LectureDao;
import cz.muni.fi.pa165.languageschool.dto.StudentTO;
import cz.muni.fi.pa165.languageschool.service.LectureService;
import cz.muni.fi.pa165.languageschool.service.StudentService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * LectureService implementation
 *
 * @author Michal Fučík (395624) michal.fuca.fucik(at)gmail.com
 */
@Service
public class LectureServiceImpl implements LectureService {

    final static Logger log = LoggerFactory.getLogger(LectureServiceImpl.class);

    private LectureDao lectureDao;
    private StudentService studentService;

    public LectureServiceImpl() {
    }

    public LectureServiceImpl(LectureDao dao) {
        if (dao == null) {
            throw new NullPointerException("Argument dao was null");
        }
        this.lectureDao = dao;
    }

    public void setLectureDao(LectureDao lectureDao) {
        if (lectureDao == null) {
            throw new NullPointerException("Argument lectureDao was null");
        }
        this.lectureDao = lectureDao;
    }
  
    public LectureDao getLectureDao() {
        return this.lectureDao;
    }

    public StudentService getStudentService() {
        return studentService;
    }

    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }

    @Transactional
    @Override
    public void createLecture(LectureTO lecture) {
        log.info("***** SERVICE Lecture createLecture: {}", lecture);
        Lecture lto = null;
        if (lecture == null) {
            throw new NullPointerException("Argument lecture is null");
        }
        try {
            lto = ETOConverter.convertLectureTO(lecture);
            lectureDao.createLecture(lto);
            lecture.setId(lto.getId());
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
    }

    @Transactional
    @Override
    public Collection<LectureTO> getAllLectures() {
        log.info("***** SERVICE Lecture getAllLectures");
        Collection<Lecture> got = null;
        Collection<LectureTO> res = new ArrayList<>();
        List<StudentTO> sts = new ArrayList<>();
        LectureTO lto = null;
        try {
            got = lectureDao.getAllLectures();
            for (Lecture l : got) {
                sts = _getEnrolledStudents(l);
                lto = ETOConverter.convertLecture(l);
                lto.setLecturer(_getLecturerTO(l));
                lto.setEnrolledStudents(sts);
                res.add(lto);
            }
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
        return res;
    }

    @Transactional
    @Override
    public LectureTO getLecture(Long id) {
        log.info("***** SERVICE Lecture getLecture: {}", id);
        LectureTO lto = null;
        if (id == null) {
            throw new NullPointerException("Argument id was null");
        }
        try {
            Lecture l = lectureDao.getLecture(id);
            List<StudentTO> sts = _getEnrolledStudents(l);
            lto = ETOConverter.convertLecture(l);
            lto.setEnrolledStudents(sts);
            lto.setLecturer(_getLecturerTO(l));
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
        return lto;

    }

    @Transactional
    @Override
    public void updateLecture(LectureTO lecture) {
        log.info("***** SERVICE Lecture updateLecture: {}", lecture);
        if (lecture == null) {
            throw new NullPointerException("Argument lecture was null");
        }
        try {
            lectureDao.updateLecture(ETOConverter.convertLectureTO(lecture));
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
    }

    @Transactional
    @Override
    public void deleteLecture(LectureTO lecture) {
        log.info("***** SERVICE Lecture deleteLecture: {}", lecture);
        if (lecture == null) {
            throw new NullPointerException("Argument lecture was null");
        }
        try {
            lectureDao.deleteLecture(ETOConverter.convertLectureTO(lecture));
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
    }

    // -- extern maintain of enrolled students vvv ----------------------------
    @Transactional
    @Override
    public List<StudentTO> getEnrolledStudents(LectureTO lec) {
        log.info("***** SERVICE Lecture getEnrolledStudents: {}", lec);
        if (lec == null) {
            throw new NullPointerException("Argument lec was null");
        }
        List<StudentTO> stos = new ArrayList<>();
        Lecture lto = ETOConverter.convertLectureTO(lec);
        try {
            stos = _getEnrolledStudents(lto);
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
        return stos;
    }

    /**
     * Fetches and converts students enrolled for given lecture
     *
     * @param lec
     * @return
     */
    @Transactional
    private List<StudentTO> _getEnrolledStudents(Lecture lec) {
        if (lec == null) {
            throw new NullPointerException("Argument lec was null");
        }
        List<StudentTO> stos = new ArrayList<>();
        try {
            List<Student> stds = lectureDao.getEnrolledStudents(lec);
            for (Student s : stds) {
                stos.add(ETOConverter.convertStudent(s));
            }

        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
        return stos;
    }

    // -- extern maintain of lecturer vvv --------------------------------------
    /**
     * Gets and converts Lecturer associated with given lecture
     *
     * @param lec
     * @return
     */
    @Transactional
    private LecturerTO _getLecturerTO(Lecture lec) {
        Lecturer lrr = null;
        if (lec == null) {
            throw new NullPointerException("Argument lec was null");
        }
        try {
            lrr = lectureDao.getLecturesLecturer(lec);
        } catch (Exception ex) {
            throw new DataFetchException(ex.toString(), ex);
        }
        return (lrr == null) ? null : ETOConverter.convertLecturer(lrr);
    }

    @Transactional
    @Override
    public LecturerTO getLecturesLecturer(LectureTO lectureTO) {
        log.info("***** SERVICE Lecture getLecturesLecturer: {}", lectureTO);
        Lecture lecture = null;
        LecturerTO lrrto = null;
        if (lectureTO == null) {
            throw new NullPointerException("Argument lecture was null");
        }
        try {
            lecture = ETOConverter.convertLectureTO(lectureTO);
            lrrto = _getLecturerTO(lecture);
        } catch (Exception ex) {
            throw new DataFetchException(ex.getMessage(), ex);
        }
        return lrrto;
    }

    @Transactional
    @Override
    public void setLecturerToLecture(Long lectureId, LecturerTO lrr) {
        log.info("***** SERVICE Lecture setLecturerToLecture: {}", lrr);
        if (lectureId == null) {
            throw new NullPointerException("Argument lectureId was null");
        }
        if (lrr == null) {
            throw new NullPointerException("Argument lrr was null");
        }
        try {
            lectureDao.setLecturerToLecture(lectureId, ETOConverter.convertLecturerTO(lrr));
        } catch (Exception ex) {
            throw new DataFetchException(ex.getMessage(), ex);
        }
    }

    @Transactional
    @Override
    public void setEnrolledStudents(Long id, List<StudentTO> ststo) {
        log.info("***** SERVICE Lecture setEnrolledStudents: {}", ststo);
        if (id == null) {
            throw new NullPointerException("Argument id was null");
        }
        if (ststo == null) {
            throw new NullPointerException("Argument ststo was null");
        }
        List<Student> sts = new ArrayList<>();

        try {
            for (StudentTO sto : ststo) {
                sts.add(ETOConverter.convertStudentTO(sto));
            }
            lectureDao.setEnrolledStudents(id, sts);
        } catch (Exception ex) {
            throw new DataFetchException(ex.getMessage(), ex);
        }
    }

    @Transactional
    @Override
    public void disenrollStudent(LectureTO lecture, StudentTO student) {
        if (lecture == null) {
            throw new NullPointerException("Argument lecture was null");
        }
        if (student == null) {
            throw new NullPointerException("Argument student was null");
        }
        try {
            
            StudentTO sto = studentService.getStudent(student.getId());
            LectureTO lto = getLecture(lecture.getId());
            sto.getEnrolledLectures().remove(lto);
            
            studentService.updateStudent(sto);
            
        } catch (Exception x) {
            throw new DataFetchException(x.getMessage(), x);
        }
    }

    @Transactional
    @Override
    public void enrollStudent(LectureTO lecture, StudentTO student) {
        if (lecture == null) {
            throw new NullPointerException("Argument lecture was null");
        }
        if (student == null) {
            throw new NullPointerException("Argument student was null");
        }
        List<StudentTO> enrolledSts = null;
        try {
            enrolledSts = getEnrolledStudents(lecture);
        } catch (Exception x) {
            throw new DataFetchException(x.getMessage(), x);
        }
        if (enrolledSts.contains(student)) {
            throw new IllegalStateException("Enrolled yet");
        }

        enrolledSts.add(student);
        try {
            setEnrolledStudents(lecture.getId(), enrolledSts);
        } catch (Exception x) {
            throw new DataFetchException(x.getMessage(), x);
        }
    }
}
